// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; version 2 of the License.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

#include "HydroApp.h"
#include "ExampleFrameListener.h"
#include "vsop.h"
#include "gravbody.h"
#include "SolarSystem.h"
#include "Object.h"
#include "physics.h"
#include "EllipticalOrbit.h"
//#include "MoonOrbit.h"
#include <vector>

//#include <ticpp.h>

#include "Clock.h"


using namespace Ogre;

int shutdown = 0;

MeshPtr createSphere(const std::string& strName, const float r, const int nRings, const int nSegments)
{
	MeshPtr pSphere = MeshManager::getSingleton().createManual(strName, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
	SubMesh *pSphereVertex = pSphere->createSubMesh();

	pSphere->sharedVertexData = new VertexData();
	VertexData* vertexData = pSphere->sharedVertexData;

	// define the vertex format
	VertexDeclaration* vertexDecl = vertexData->vertexDeclaration;
	size_t currOffset = 0;
	// positions
	vertexDecl->addElement(0, currOffset, VET_FLOAT3, VES_POSITION);
	currOffset += VertexElement::getTypeSize(VET_FLOAT3);
	// normals
	vertexDecl->addElement(0, currOffset, VET_FLOAT3, VES_NORMAL);
	currOffset += VertexElement::getTypeSize(VET_FLOAT3);
	// two dimensional texture coordinates
	vertexDecl->addElement(0, currOffset, VET_FLOAT2, VES_TEXTURE_COORDINATES, 0);
	currOffset += VertexElement::getTypeSize(VET_FLOAT2);

	// allocate the vertex buffer
	vertexData->vertexCount = (nRings + 1) * (nSegments+1);
	HardwareVertexBufferSharedPtr vBuf = HardwareBufferManager::getSingleton().createVertexBuffer(vertexDecl->getVertexSize(0), vertexData->vertexCount, HardwareBuffer::HBU_STATIC_WRITE_ONLY, false);
	VertexBufferBinding* binding = vertexData->vertexBufferBinding;
	binding->setBinding(0, vBuf);
	float* pVertex = static_cast<float*>(vBuf->lock(HardwareBuffer::HBL_DISCARD));

	// allocate index buffer
	pSphereVertex->indexData->indexCount = 6 * nRings * (nSegments + 1);
	pSphereVertex->indexData->indexBuffer = HardwareBufferManager::getSingleton().createIndexBuffer(HardwareIndexBuffer::IT_16BIT, pSphereVertex->indexData->indexCount, HardwareBuffer::HBU_STATIC_WRITE_ONLY, false);
	HardwareIndexBufferSharedPtr iBuf = pSphereVertex->indexData->indexBuffer;
	unsigned short* pIndices = static_cast<unsigned short*>(iBuf->lock(HardwareBuffer::HBL_DISCARD));

	float fDeltaRingAngle = (PI / nRings);
	float fDeltaSegAngle = (2 * PI / nSegments);
	unsigned short wVerticeIndex = 0 ;

	// Generate the group of rings for the sphere
	for( int ring = 0; ring <= nRings; ring++ ) {
		float r0 = r * sinf (ring * fDeltaRingAngle);
		float z0 = r * cosf (ring * fDeltaRingAngle);

		// Generate the group of segments for the current ring
		for(int seg = 0; seg <= nSegments; seg++) {
			float y0 = r0 * sinf(seg * fDeltaSegAngle);
			float x0 = r0 * cosf(seg * fDeltaSegAngle);

			// Add one vertex to the strip which makes up the sphere
			*pVertex++ = x0;
			*pVertex++ = y0;
			*pVertex++ = z0;

			Vector3 vNormal = Vector3(x0, y0, z0).normalisedCopy();
			*pVertex++ = vNormal.x;
			*pVertex++ = vNormal.y;
			*pVertex++ = vNormal.z;

			*pVertex++ = (float) seg / (float) nSegments;
			*pVertex++ = (float) ring / (float) nRings;

			if (ring != nRings) {
                               // each vertex (except the last) has six indices pointing to it
				*pIndices++ = wVerticeIndex + nSegments + 1;
				*pIndices++ = wVerticeIndex;               
				*pIndices++ = wVerticeIndex + nSegments;
				*pIndices++ = wVerticeIndex + nSegments + 1;
				*pIndices++ = wVerticeIndex + 1;
				*pIndices++ = wVerticeIndex;
				wVerticeIndex ++;
			}
		}; // end for seg
	} // end for ring

	// Unlock
	vBuf->unlock();
	iBuf->unlock();
	// Generate face list
	pSphereVertex->useSharedVertices = true;

	// the original code was missing this line:
	pSphere->_setBounds( AxisAlignedBox( Vector3(-r, -r, -r), Vector3(r, r, r) ), false );
	pSphere->_setBoundingSphereRadius(r);
        // this line makes clear the mesh is loaded (avoids memory leaks)
    pSphere->load();
	return pSphere;
 }


HydroApp::HydroApp()
{
	mRoot = 0;
	mFrameListener = 0;
	//bodies = new std::vector<GravBody*>();
}

HydroApp::~HydroApp()
{
	if (mRoot)
		delete mRoot;
	if (mFrameListener)
		delete mFrameListener;
}

GravBody* HydroApp::createPlanet(Body* parent, std::string name, Orbit* orbit, double mass, double radius, double rotperiod, double rotoffset, double rotinclination, double rotnode)
{
	GravBody* tmp = new GravBody(name, parent, orbit, mass, radius, rotperiod, rotoffset, rotinclination, rotnode);
	return tmp;
}
#ifndef PI
#define PI 3.141592653589793238462643383279502884
#endif
#define Deg2Rad(x) (x*(double)PI/(double)180.)


void HydroApp::createScene()
{
	VSOP87Solver* vsopsolver = new VSOP87Solver();
	staticNode = mSceneMgr->getRootSceneNode()->createChildSceneNode();
	rootBody = new SolarSystem(NULL);
	
	/*try {
		ticpp::Document test( "test.xml" );
		test.LoadFile();
		ticpp::Element* pElem = test.FirstChildElement()->NextSiblingElement();
		std::string attr;
		pElem->GetAttribute( "myAttribute", &attr );
		LogManager::getSingleton().logMessage(attr);
	} catch(ticpp::Exception& ex ) {
		LogManager::getSingleton().logMessage(ex.what());
	} */

	//GravBody* mercury = createPlanet(rootBody, mSceneMgr, "mercury", vsopsolver->getOrbit("mercury"), 3.3018800000000001e+023, 2440000., 1407.509405/24, Deg2Rad(291.20), Deg2Rad(7.01), Deg2Rad(48.42));
/*	mercury->node = mSceneMgr->getRootSceneNode()->createChildSceneNode();
	createSphere("mercury", 400, 64, 64);
	Entity* mercuryEntity = mSceneMgr->createEntity("mercuryEntity", "mercury");
	mercuryEntity->setMaterialName("mercury");
	mercury->node->attachObject(mercuryEntity);
	mercury->node->pitch(Radian(90)); */
	GravBody* venus = createPlanet(rootBody, "venus", vsopsolver->getOrbit("venus"), 4.86855374e+024, 6051840., 5832.479839/24, Deg2Rad(137.45), Deg2Rad(178.78), Deg2Rad(300.22));
	//venus->node = mSceneMgr->getRootSceneNode()->createChildSceneNode();
	GravBody* earth = createPlanet(rootBody, "earth", vsopsolver->getOrbit("earth"), 5.973698968e+024, 6371010.);
	//earth->node = mSceneMgr->getRootSceneNode()->createChildSceneNode();
	GravBody* mars = createPlanet(rootBody, "mars", vsopsolver->getOrbit("mars"), 6.4185419999999998e+023, 3389920., 24.622962/24, Deg2Rad(136.005), Deg2Rad(26.72), Deg2Rad(82.91));
	//mars->node = mSceneMgr->getRootSceneNode()->createChildSceneNode();
	GravBody* jupiter = createPlanet(rootBody, "jupiter", vsopsolver->getOrbit("jupiter"), 1.8986111e+027, 69911000., 9.927953/24, Deg2Rad(342), Deg2Rad(2.222461), Deg2Rad(-22.203));
	//jupiter->node = mSceneMgr->getRootSceneNode()->createChildSceneNode();
	//GravBody* saturn = createPlanet(rootBody, mSceneMgr, "saturn", vsopsolver->getOrbit("saturn"), 5.6846272000000002e+026, 58232000., 10.65622/24, Deg2Rad(358.922), Deg2Rad(28.049), Deg2Rad(169.530));
	//saturn->node = mSceneMgr->getRootSceneNode()->createChildSceneNode();
	//GravBody* uranus = createPlanet(mSceneMgr, "uranus", vsopsolver->getOrbit("uranus"), 8.6832053999999996e+025, 25362000.);
	//uranus->node = mSceneMgr->getRootSceneNode()->createChildSceneNode();
	//GravBody* neptune = createPlanet(mSceneMgr, "neptune", vsopsolver->getOrbit("neptune"), 1.024569e+026, 24624000.);
	//neptune->node = mSceneMgr->getRootSceneNode()->createChildSceneNode();
	GravBody* sol = createPlanet(rootBody, "sol", vsopsolver->getOrbit("sol"), 1.9889194443999999e+030, 696000000., 609.12/24, Deg2Rad(23.00), 0, Deg2Rad(75.77));
	//Orbit* cerorbit = new EllipticalOrbit(380974674207.59998, 0.0793, Deg2Rad(10.584), Deg2Rad(80.483), Deg2Rad(74.043), Deg2Rad(232.067), 4.60014*365, 2452600.5);
	//GravBody* ceres = createPlanet(sol, mSceneMgr, "ceres", cerorbit, 9.43e+20, 487000, 9.074170/24, Deg2Rad(339.85), Deg2Rad(11), Deg2Rad(29));
	//ceres->rotepoch = 2449249.91125;
	//Orbit* moonorbit = new EllipticalOrbit(earth, 36362510300.95120001, 0.0549006, Deg2Rad(5.145), Deg2Rad(3.187815650162177e+02), Deg2Rad(1.155170954554251e+02), Deg2Rad(6.196289707758003e+01), 2.723467814861709e+01, 2454653.500000000);
	//Orbit* moonorbit = new MoonOrbit(earth);
	//GravBody* moon = createPlanet(earth, mSceneMgr, "moon", moonorbit, 734.9e+20, 1737530, 0, Deg2Rad(38), Deg2Rad(23.45), 0.0 );
	//Orbit* testorbit = new EllipticalOrbit(67670000000, 0.0016886, Deg2Rad(0. /*51.5684*/), Deg2Rad(343.1518), Deg2Rad(346.2476), Deg2Rad(13.8216), 0.064176392, 2452028.18381755);
	//GravBody* testcraft = createPlanet(earth, mSceneMgr, "test", testorbit, 123, 1);
 
	//sol->node = mSceneMgr->getRootSceneNode()->createChildSceneNode();
	/*
	bodies->push_back(sol);
	bodies->push_back(mercury);
	bodies->push_back(venus);
	bodies->push_back(earth);
	bodies->push_back(mars);
	bodies->push_back(jupiter);
	bodies->push_back(saturn);
	bodies->push_back(ceres);
	bodies->push_back(moon);
	*/
	rootBody->children->push_back(sol);
	//rootBody->children->push_back(mercury);
	rootBody->children->push_back(venus);
	rootBody->children->push_back(earth);
	rootBody->children->push_back(mars);
	rootBody->children->push_back(jupiter);
	//rootBody->children->push_back(saturn);
	//sol->children->push_back(ceres);
	//earth->children->push_back(moon);
	//earth->children->push_back(testcraft);

	asteroid = new Object(earth, staticNode->createChildSceneNode());
	createSphere("asteroid", 100, 64, 64);
	Entity* asteroidEntity = mSceneMgr->createEntity("asteroid", "asteroid");
	asteroidEntity->setMaterialName("asteroid");
	asteroid->node->attachObject(asteroidEntity);

	Light* l = mSceneMgr->createLight("MainLight");
	l->setPosition(0,0,0);

	std::ifstream stardata;
	stardata.open ("stars.bin", std::ios_base::binary);
	int size;
	float tmpfloat;
	stardata.read((char*)&size, 4);
	float x, y, z;
	float r, g, b;
	//Ogre::LogManager::getSingleton().logMessage(Ogre::StringConverter::toString(size));

	//ManualObject* stars = app->mSceneMgr->createManualObject("stars");
	//stars->estimateVertexCount(size);
	//stars->estimateIndexCount(size);
	//stars->begin("star", Ogre::RenderOperation::OT_POINT_LIST);

	BillboardSet* stars = mSceneMgr->createBillboardSet("stars", size);
	stars->setDefaultDimensions(500, 500);
	stars->setVisible(true);
	stars->setPointRenderingEnabled(true);
	stars->setMaterialName("star");
	for (int i = 0; i < size; i++) {
	stardata.read((char*)&tmpfloat, 4);
	x = tmpfloat*1.08568025e5;
	stardata.read((char*)&tmpfloat, 4);
	y = tmpfloat*1.08568025e5;
	stardata.read((char*)&tmpfloat, 4);
	z = tmpfloat*1.08568025e5;
	//stars->position(x, y, z);
	stardata.read((char*)&tmpfloat, 4);
	r = tmpfloat;
	stardata.read((char*)&tmpfloat, 4);
	g = tmpfloat;
	stardata.read((char*)&tmpfloat, 4);
	b = tmpfloat;
	stars->createBillboard(Vector3(x, y, z), ColourValue(r, g, b));
	}
	Ogre::LogManager::getSingleton().logMessage(Ogre::StringConverter::toString(stars->getNumBillboards()));
	//stars->end();
	stardata.close();

	staticNode->attachObject(stars);


	//bodies->push_back(neptune);
}

void HydroApp::go()
{
	clock = new Clock(2451545.0);
	if (!setup())
		return;
	mRoot->getRenderSystem()->_initRenderTargets();
	mRoot->clearEventTimes();

	//mRoot->startRendering();
	
//	HANDLE physthread;
//	DWORD threadid;
	std::vector<GravBody*>::iterator iter;
//	std::vector<PhysBody*>* physbodies = new std::vector<PhysBody*>();
//	for (iter = bodies->begin(); iter != bodies->end(); iter++) {
//		physbodies->push_back((*iter)->physBody);
//	}
//	physthread = CreateThread(NULL, 0, physicsMain, (void*)physbodies, 0, &threadid);
//	if (physthread == NULL) {
//		Ogre::LogManager::getSingleton().logMessage("Couldn't create physics thread! Abort! Abort!");
//		return;
//	}



	//Object* test = new Object();
//	addObject(test->physObject);
	clock->reset();
	clock->timeaccel = 1000000.;
	while (1) {
		WindowEventUtilities::messagePump();
		clock->tick();
		//for (iter = bodies->begin(); iter != bodies->end(); iter++) {
		//	(*iter)->update();
		//}
		rootBody->update();
		asteroid->update();
		//mFrameListener->mDebugText = StringConverter::toString((Real)clock->jd, 20);
		mWindow->setActive(true);
		if (!mRoot->renderOneFrame())
			break;
	}
//	WaitForSingleObject(exit_mutex, INFINITE);
//	shutdown = 1;
//	ReleaseMutex(exit_mutex);
//	WaitForSingleObject(physthread, INFINITE);
//	CloseHandle(physthread);
	//for (iter = bodies->begin(); iter != bodies->end(); iter++) {
	//	delete (*iter);
	//}
	delete rootBody;
	//delete asteroid;
	//physbodies->clear();
	//bodies->clear();
//	delete physbodies;
	//delete bodies;
	//delete test;
	//timeEndPeriod(1);
}

bool HydroApp::setup()
{
	mRoot = new Root("plugins.cfg", "ogre.cfg", "Ogre.log");
	setupResources();
	if(!configure())
		return false;
	mSceneMgr = mRoot->createSceneManager(ST_GENERIC);
	mCamera = mSceneMgr->createCamera("MainCam");
	mCamera->setPosition(Vector3(0,0,40000));
	mCamera->lookAt(Vector3(0,0,0));
	mCamera->setNearClipDistance(30000);
	mCamera->setFarClipDistance(90000000);
	TextureManager::getSingleton().setDefaultNumMipmaps(5);
	//root->initialise(false);
	//RenderWindow *renderWindow = root->createRenderWindow("Main",320,240,false);
	Viewport *vp = mWindow->addViewport(mCamera);
	vp->setBackgroundColour(ColourValue(0,0,0));
	mCamera->setAspectRatio(Real(vp->getActualWidth()) / Real(vp->getActualHeight()));
	loadResources();
	mFrameListener= new ExampleFrameListener(mWindow, mCamera, clock);
	mFrameListener->showDebugOverlay(true);
	mRoot->addFrameListener(mFrameListener);
	mSceneMgr->setAmbientLight(ColourValue(0.5, 0.5, 0.5));
    // Create a skybox
    //mSceneMgr->setSkyBox(true, "Examples/SpaceSkyBox", 50 );
	createScene();
	return true;
}

bool HydroApp::configure()
{
	if(mRoot->showConfigDialog())
	{
		mWindow = mRoot->initialise(true);
		return true;
	}
	else
	{
		return false;
	}
}

void HydroApp::setupResources(void)
{
    // Load resource paths from config file
    ConfigFile cf;
    cf.load("resources.cfg");

    // Go through all sections & settings in the file
    ConfigFile::SectionIterator seci = cf.getSectionIterator();

    String secName, typeName, archName;
    while (seci.hasMoreElements())
    {
        secName = seci.peekNextKey();
        ConfigFile::SettingsMultiMap *settings = seci.getNext();
        ConfigFile::SettingsMultiMap::iterator i;
        for (i = settings->begin(); i != settings->end(); ++i)
        {
            typeName = i->first;
            archName = i->second;
#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE
            // OS X does not set the working directory relative to the app,
            // In order to make things portable on OS X we need to provide
            // the loading with it's own bundle path location
            ResourceGroupManager::getSingleton().addResourceLocation(
                String(macBundlePath() + "/" + archName), typeName, secName);
#else
            ResourceGroupManager::getSingleton().addResourceLocation(
                archName, typeName, secName);
#endif
        }
    }
}

void HydroApp::loadResources()
{
	ResourceGroupManager::getSingleton().initialiseAllResourceGroups();
}